#include <iostream>
#include <thread>
#include <mutex>
#include <string>
#include <deque>
#include <atomic>
#include <queue>
#include <sstream>
#include <cassert>

#include "package.h"
#include "uniform_socket.h"
#include "types.h"

using namespace std;
mutex mmsg, minput, moutput, mres, msend;
queue<string> inputQueue;
queue<package> sendQueue;
queue<package> msgQueue;
queue<package> resQueue;
atomic_bool connected;
atomic_bool retSignal;
atomic_int id;
atomic<unsigned int> stamp;


void output_menu()
{
	puts("Please select one of these functions (a-g)");
	puts("\ta) Connect");
	puts("\tb) Disconnect");
	puts("\tc) Get server time");
	puts("\td) Get server name");
	puts("\te) Get active connection list");
	puts("\tf) Send message");
	puts("\tg) Exit");
}


void Recv(uniform_socket_t s)
{
	for (;;)
	{
		package p;
		int len = recv(s, (char*)&p, bufSize, 0);
		if (len == 0)
		{
			connected = false;
			id = 0;
			return;
		}
		if ((p.type & 0xf0) == TYPE_RES)
		{
			if (p.num == 0)
			{
				mres.lock();
			}
			resQueue.push(p);
			if (p.num == p.tot - 1)
			{
				mres.unlock();
			}
		}
		else if ((p.type & 0xf0) == TYPE_IND)
		{
			if (p.isFirst())
			{
				mmsg.lock();
			}
			msgQueue.push(p);
			if (p.isLast())
			{
				mmsg.unlock();
			}
		}
	}
}

void Input()
{
	for (;;)
	{
		char c;
		cin >> c;
		switch (c)
		{
			case 'a':
			{
				string addr;
				string port;
				cin >> addr >> port;
				//Todo: check input correcteness
				if (false)
				{
					puts("Error input");
				}
				minput.lock();
				inputQueue.push("a " + addr + " " + port);
				minput.unlock();
				break;
			}
			case 'b':
			case 'c':
			case 'd':
			case 'e':
			case 'g':
			{
				string s = "";
				s += c;
				minput.lock();
				inputQueue.push(s);
				minput.unlock();
				break;
			}
			case 'f':
			{
				string s;
				getline(cin, s);
				minput.lock();
				inputQueue.push("f" + s);
				minput.unlock();
				break;
			}
			default:
			{
				puts("Error Input");
				output_menu();
				break;
			}
		}
	}
}

struct client
{
	uint id;
	string addr;
	int port;
};

vector<client> clientList;

int main()
{
	uniform_socket_init();

	uniform_socket_t clntSocket = socket(AF_INET, SOCK_STREAM, 0);
	puts("Client ready");
	output_menu();
	thread t_input(Input);
	for (;;)
	{
		if (msgQueue.empty() && inputQueue.empty())
		{
			continue;
		}
		if (!inputQueue.empty())
		{
			stringstream sin(inputQueue.front());
			minput.lock();
			inputQueue.pop();
			minput.unlock();
			char c;
			sin >> c;
			if (c == 'a')
			{
				if (connected)
				{
					puts("Already connected!");
					continue;
				}
				string addr;
				int port;
				sin >> addr >> port;
				sockaddr_in seraddr;
				memset(&seraddr, 0, sizeof(seraddr));
				seraddr.sin_family = AF_INET;
				seraddr.sin_addr.s_addr = inet_addr(addr.c_str());
				seraddr.sin_port = htons(port);
				int ret = connect(clntSocket, (const sockaddr *)&seraddr, sizeof(seraddr));
				if (ret < 0)
				{
					puts("Connection fail");
					printf("errno = %d\n", errno);
					continue;
				}

				puts("Connection success");
				retSignal = false;
				clientList.clear();
				thread t(Recv, clntSocket);
				t.detach();
				while (resQueue.empty()) {}
				// wait till get hello message from server
				char* helloStr = nullptr;
				uint helloStrLen = 0;
				const int helloStrIdOffset = 18;
				// "Hello! Your id is:%d.";
				mres.lock();
				compose(resQueue, helloStr, helloStrLen);
				mres.unlock();
				int tmpid;
				sscanf(helloStr + helloStrIdOffset, "%d", &tmpid);
				id = tmpid;
				connected = true;
				puts(helloStr);
				delete[] helloStr;
			}
			else
			{
				if (!connected)
				{
					puts("Not connected!");
					continue;
				}
				if (c != 'f')
				{
					u8 type = TYPE_ASK | (c - 'a');
					package p(type, id, 0, stamp++, 0, 1, nullptr);
					send(clntSocket, (char*)&p, p.len, 0);
					while (resQueue.empty()) {}
					char* resStr = nullptr;
					uint resLen = 0;
					mres.lock();
					compose(resQueue, resStr, resLen);
					mres.unlock();
					switch (c)
					{
						case 'b':
						case 'g':
						{
							puts(resStr);
							puts("Disconnected from server");
							connected = false;
							retSignal = true;
							while (retSignal) {}
							uniform_socket_close(clntSocket);
							if (c == 'g')
							{
								uniform_socket_cleanup();
								return 0;
							}
							break;
						}
						case 'c':
						case 'd':
						{
							puts(resStr);
							break;
						}
						case 'e':
						{
							int n = 0;
							stringstream cListss(resStr);
							cListss >> n;
							clientList.resize(n);
							for (int i = 0; i < n; i++)
							{
								cListss >> clientList[i].id >> clientList[i].addr >> clientList[i].port;
							}
							for (int i = 0; i < n; i++)
							{
								cout << clientList[i].id << '\t' << clientList[i].addr << '\t' << clientList[i].port << endl;
							}
							break;
						}
						default:
						{
							break;
						}
					}
					delete[] resStr;
				}
				else
				{
					if (clientList.empty())
					{
						puts("No client list!");
						continue;
					}
					int did;
					string msg;
					sin >> did;
					if (did >= clientList.size())
					{
						puts("No such id!");
						continue;
					}
					did = clientList[did].id;
					msg = std::to_string(did) + " ";
					while (sin.get() != '\"') {}
					bool flag = false;
					while (true)
					{
						char c = sin.get();
						if (c == '\\')
						{
							flag = ~flag;
						}
						if (flag)
						{
							flag = false;
						}
						if (c == '\"')
						{
							break;
						}
						msg += c;
					}
					u8 type = TYPE_ASK | ('f' - 'a');
					split(sendQueue, type, did, stamp++, msg.c_str(), msg.length());
					while (!sendQueue.empty())
					{
						send(clntSocket, (char*)&sendQueue.front(), sendQueue.front().len, 0);
						sendQueue.pop();
					}

					while (resQueue.empty()){}
					char* resStr = nullptr;
					uint resLen = 0;
					mres.lock();
					compose(resQueue, resStr, resLen);
					mres.unlock();
					puts(resStr);
					delete[] resStr;
				}
			}
		}
		if (!msgQueue.empty())
		{
			while (!msgQueue.empty())
			{
				char* msgStr = nullptr;
				uint msgLen = 0;
				int from = msgQueue.front().from;
				int stamp = msgQueue.front().stamp;
				mmsg.lock();
				compose(msgQueue, msgStr, msgLen);
				mmsg.unlock();
				printf("Message from client %d:", from);
				puts(msgStr);
				delete[] msgStr;
				char msgRcvdStr[50];
				sprintf(msgRcvdStr, "Message recieved by client %d!", id.load());
				package indRecievedPackage(TYPE_RES | ('f' - 'a'), from, strlen(msgRcvdStr) + 1, stamp, 0, 1, msgRcvdStr);
				send(clntSocket, (char*)&indRecievedPackage, indRecievedPackage.len, 0);
			}
		}
	}
}
